package com.wb.utils

import com.wb.component.Slf4j.Companion.log
import com.wb.vo.wy.AccountInfo
import com.wb.vo.wy.WYSearchSong
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.stereotype.Component
import java.util.*
import javax.annotation.Resource
import kotlin.collections.ArrayList
import kotlin.collections.HashMap

/**
 * 网抑云音乐工具
 *
 * @author 祁雪
 */
@Component
class WYMusicUtil {

    @Resource(name = "redisDBA")
    fun setRedisTemplate(redisTemplate: RedisTemplate<String, Any>) {
        WYMusicUtil.redisTemplate = redisTemplate
    }

    companion object {
        lateinit var redisTemplate: RedisTemplate<String, Any>

        var botWYKey = "LSE_BOT_WY"

        /**
         * 网抑云音乐部分api
         * 调用参考桌面/文档资料/网抑云api详解.md
         */
        val WY_DATA_MAP = hashMapOf<String, String>(
            // 音乐搜索
            "searchUrl" to "https://music.163.com/weapi/cloudsearch/get/web?csrf_token=",
            // 检查音乐是否能够播放 调用 musicUrl前获取
            "checkMusicIsPlayUrl" to "https://music.163.com/weapi/song/enhance/player/url",
            // 获取音乐播放链接
            "musicUrl" to "https://interface3.music.163.com/eapi/song/enhance/player/url",
            // 音乐页面地址 即jump落地页 id = 音乐ID
            "jumpUrl" to "https://y.music.163.com/m/song?id=",
            // 音乐播放链接 部分音乐可能没有 id = 音乐ID
            "musicPlayUrl" to "https://music.163.com/song/media/outer/url?id=",
            // 网易热评 后面需要跟 wyCommentResourceMap.get(?)音乐ID等
            "hotCommentMusicUrl" to "https://music.163.com/weapi/v1/resource/hotcomments/",
            // 网易账户登录 - 目前仅限 手机+密码
            "loginByPhoneUrl" to "https://music.163.com/weapi/login/cellphone",
            // 获取用户喜欢的音乐列表 - 无序
            "userLikeList" to "https://music.163.com/weapi/song/like/get",
            // 获取音乐详细信息 (不包含播放链接)
            "musicInfoUrl" to "https://music.163.com/weapi/v3/song/detail"
        )

        /**
         * 网易评论资源对照
         */
        val wyCommentResourceMap = hashMapOf(
            // 单曲
            "music" to "R_SO_4_",
            // MV
            "mv" to "R_MV_5_",
            // 歌单
            "playList" to "A_PL_0_",
            // 专辑
            "album" to "R_AL_3_",
            // 电台
            "dj" to "A_DJ_1_",
            // 视频
            "video" to "R_VI_62_",
            // 动态
            "dynamic" to "A_EV_2_"
        )

        /**
         * 获取音乐热评
         */
        fun getHotMusicComment(song: WYSearchSong): String {
            // 生成key
            val map = hashMapOf<String, Any>(
                "rid" to song.id.toString(),
                "beforeTime" to 0,
                "limit" to 10,
                "offset" to 0
            )
            val gson = Gson()
            val paramMap = WangYiAESEncrypt.encryptWY(gson.toJson(map))

            val result = RestUtil.send(
                "${WY_DATA_MAP.get("hotCommentMusicUrl")}" +
                        "${wyCommentResourceMap.get("music")}${song.id}",
                paramMap, null, 1, String::class.java
            ).body

            var msg = "很抱歉,暂未获取到热评"
            val resultData = gson.fromJson(
                result,
                object : TypeToken<HashMap<String, Any>>() {}.type
            ) as HashMap<String, Any>

            if (resultData.containsKey("code") && resultData.containsKey("hotComments") &&
                resultData.get("code").toString().toBigDecimal().toInt() == 200
            ) {

                val list = gson.fromJson(
                    gson.toJson(resultData.get("hotComments")),
                    object : TypeToken<ArrayList<HashMap<String, Any>>>() {}.type
                ) as ArrayList<HashMap<String, Any>>
                if (list.size == 0) {
                    return msg
                }
                var index = 0
                while (true) {
                    if (index >= 3) {
                        return msg
                    }
                    val num = (Math.random() * list.size).toInt()
                    val dataMap = list.get(num)
                    if (dataMap.containsKey("content")) {
                        msg = "【${song.name}】\n" +
                                "${dataMap.get("content")}\n" +
                                "点赞量: ${dataMap.getOrDefault("likedCount", "---")}"
                        return msg
                    }
                    index++
                }
            }
            return msg
        }

        /**
         * 搜索音乐
         */
        fun searchMusic(keyword: String, offset: Int): ArrayList<WYSearchSong> {
            // 生成key
            val map = hashMapOf<String, Any?>(
                "s" to keyword,
                // 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频
                "type" to 1,
                // 每页数量
                "limit" to 10,
                // 偏移量
                "offset" to offset
            )
            val gson = Gson()

            var songs = arrayListOf<WYSearchSong>()
            val paramMap = WangYiAESEncrypt.encryptWY(gson.toJson(map))

            val resultEn = RestUtil.send(WY_DATA_MAP.get("searchUrl")!!, paramMap, null, 1, String::class.java)

            val result = resultEn.body

            var resultData = gson.fromJson(
                result,
                object : TypeToken<HashMap<String, Any>>() {}.type
            ) as HashMap<String, Any>

            if (resultData.containsKey("code") && resultData.containsKey("result") &&
                resultData.get("code").toString().toBigDecimal().toInt() == 200
            ) {

                resultData = gson.fromJson(
                    gson.toJson(resultData.get("result")),
                    object : TypeToken<HashMap<String, Any>>() {}.type
                ) as HashMap<String, Any>

                if (resultData.containsKey("songs")) {
                    songs = gson.fromJson(
                        gson.toJson(resultData.get("songs")),
                        object : TypeToken<ArrayList<WYSearchSong>>() {}.type
                    ) as ArrayList<WYSearchSong>
                }
            }
            return songs
        }

        fun getCatCode(
            songWY: WYSearchSong
        ): String {
            // 网抑云音乐分享 猫猫码参考
            // [CAT:other,code=[mirai:origin:MUSIC_SHARE]] 音乐分享标准前缀
            // 内容实体 猫猫码
            // [CAT:music,kind=绑定得卡片名称 网抑云就是 【neteaseCloud】,musicUrl=音乐播放地址,
            // jumpUrl=jump落地页,title=音乐名称,
            // pictureUrl=音乐缩略图地址,summary=歌手名称,brief=[分享]音乐名称]
            val ar = songWY.ar.map { it.name }
                .joinToString(" ")

            var msg = "[CAT:other,code=[mirai:origin:MUSIC_SHARE]]"

            msg = msg + "[CAT:music,kind=neteaseCloud,musicUrl=${WY_DATA_MAP.get("musicPlayUrl") + songWY.id}," +
                    "jumpUrl=${WY_DATA_MAP.get("jumpUrl") + songWY.id}," +
                    "pictureUrl=${if (songWY.al != null) songWY.al!!.picUrl else ""}," +
                    "summary=${ar},title=${songWY.name}," +
                    "brief=分享-${songWY.name}]"
            return msg
        }

        /**
         * 手机号码登录
         */
        fun loginWYByPhone(
            qqNumber: String,
            account: String,
            password: String
        ): AccountInfo? {

            val gson = Gson()

            var accountInfo: AccountInfo? = null;

            if (redisTemplate.opsForHash<String, Any>().hasKey(botWYKey, qqNumber)) {

                accountInfo = gson.fromJson(
                    redisTemplate.opsForHash<String, Any>().get(botWYKey, qqNumber).toString(),
                    object : TypeToken<AccountInfo>() {}.type
                ) as AccountInfo

                if (Math.abs(DateUtil.getDateMin(accountInfo.saveDateTime, Date())) < 30) {
                    log.info("[网易登录]查询到缓存的登录信息: ${accountInfo}")
                    return accountInfo
                }
            }

            log.info("登录网易账户: ${account}")

            val headerMap = hashMapOf(
                "User-Agent" to "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.30 Safari/537.36",
                "Content-Type" to "application/x-www-form-urlencoded",
                "Referer" to "https://music.163.com",
                "Cookie" to if (accountInfo != null && accountInfo.cookie.isNotEmpty()) {
                    val cookie = arrayListOf<String>()
                    accountInfo.cookie.forEach { k, v ->
                        cookie.add("${k}=${v}")
                    }
                    cookie.add("os=pc")
                    cookie.joinToString(separator = ";")
                } else {
                    "__remember_me=true; NMTID=00OrvjGWwM8COleD0iiqAv-yujAYykAAAF680MUGg; __csrf=50dca1a5716cbaae4e20ae0fd9207e16; MUSIC_U=5629984795c19c58b2bc87add80d1473bf6d8d5227be082ca414265319b2cbf233a649814e309366;os=pc"
                }
            )

            println("Cookie: ${headerMap.get("Cookie")}")

            val response = RestUtil.send(
                WY_DATA_MAP.get("loginByPhoneUrl")!!,
                WangYiAESEncrypt.encryptWY(
                    Gson().toJson(
                        hashMapOf(
                            "phone" to account,
                            "countrycode" to "86",
//                            "password" to WangYiAESEncrypt.md5Encode(password),
                            "password" to password,
                            "rememberLogin" to "true",
                            "csrf_token" to if (accountInfo != null && accountInfo.cookie.isNotEmpty()) {
                                accountInfo.cookie.get("__csrf")
                            } else {
                                ""
                            }
                        )
                    )
                ), headerMap, 1, String::class.java
            )


            val cookieMap = response.headers
            val content = response.body

            val map = gson.fromJson(
                content,
                object : TypeToken<HashMap<String, Any>>() {}.type
            ) as HashMap<String, Any>

            if (map.get("code").toString().toBigDecimal().toInt() != 200) {
                log.warn(content)
                return accountInfo
            }

            accountInfo = gson.fromJson(
                gson.toJson(map.get("account")),
                object : TypeToken<AccountInfo>() {}.type
            ) as AccountInfo

            accountInfo.token = map.get("token").toString()

            println(cookieMap)

            cookieMap.get("set-cookie")?.forEach {
                val strs = it.split(";")

                strs.forEach { data ->
                    val kvs = data.split("=")
                    accountInfo.cookie.put(kvs[0], kvs[1])
                }
            }

            accountInfo.id = accountInfo.id.toBigDecimal().toLong().toString()
            accountInfo.saveDateTime = Date()

            log.info("登录成功: ${accountInfo}")
            redisTemplate.opsForHash<String, Any>().put(botWYKey, qqNumber, gson.toJson(accountInfo))

            return accountInfo
        }

        /**
         * 获取我喜欢的音乐
         */
        fun getMyLikeMusic(
            qqNumber: String,
            account: String,
            password: String,
            isRandom: Boolean
        ): String {

            val accountInfo = loginWYByPhone(qqNumber, account, password)

            if (accountInfo == null) {
                return "登录失败,请检查已绑定的账户信息";
            }
            log.info("获取网抑云账户${accountInfo.id}喜欢的音乐")

            val cookie = if (accountInfo.cookie.isNotEmpty()) {
                val cookie = arrayListOf<String>()
                accountInfo.cookie.forEach { k, v ->
                    cookie.add("${k}=${v}")
                }
                cookie.add("os=pc")
                cookie.joinToString(separator = ";")
            } else {
                "__remember_me=true; NMTID=00OrvjGWwM8COleD0iiqAv-yujAYykAAAF680MUGg; __csrf=50dca1a5716cbaae4e20ae0fd9207e16; MUSIC_U=5629984795c19c58b2bc87add80d1473bf6d8d5227be082ca414265319b2cbf233a649814e309366;os=pc"
            }

            val param = WangYiAESEncrypt.encryptWY(
                Gson().toJson(
                    hashMapOf(
                        "uid" to accountInfo.id,
                        "csrf_token" to if (accountInfo.cookie.isNotEmpty()) {
                            accountInfo.cookie.get("__csrf")
                        } else {
                            ""
                        }
                    )
                )
            )

            val headerMap = hashMapOf(
                "User-Agent" to "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.30 Safari/537.36",
                "Content-Type" to "application/x-www-form-urlencoded",
                "Referer" to "https://music.163.com",
                "Cookie" to cookie
            )

            println("Cookie: ${headerMap.get("Cookie")}")

            val response = RestUtil.send(
                WY_DATA_MAP.get("userLikeList")!!,
                param, headerMap, 1, String::class.java
            )

            val gson = Gson()

            val content = response.body

            val map = gson.fromJson(
                content,
                object : TypeToken<HashMap<String, Any>>() {}.type
            ) as HashMap<String, Any>

            if (map.get("code").toString().toBigDecimal().toInt() != 200) {
                log.warn("获取网抑云账户${accountInfo}喜欢的音乐失败", content)
                return "获取失败,请稍后再试"
            }

            val ids = gson.fromJson(
                gson.toJson(map.get("ids")),
                object : TypeToken<List<String>>() {}.type
            ) as List<String>

            if (ids.isNullOrEmpty()) {
                return "你还没有喜欢的音乐"
            }

            val id = if (isRandom) {
                val num = (Math.random() * ids.size).toInt()
                ids[num]
            } else {
                ids[0]
            }.toBigDecimal().toLong().toString()

            val song = getMusicInfo(arrayOf(id), headerMap)

            if (song == null) {
                log.warn("解析音乐${id}详情失败")
                return "解析音乐详情数据失败";
            }

            return getCatCode(song)
        }

        /**
         * 获取音乐详细信息
         */
        fun getMusicInfo(
            ids: Array<String>,
            headerMap: HashMap<String, String>
        ): WYSearchSong? {

            val gson = Gson()

            val idList = arrayListOf<HashMap<String, String>>()
            ids.forEach {
                idList.add(
                    hashMapOf(
                        "id" to it
                    )
                )
            }
            val param = WangYiAESEncrypt.encryptWY(
                gson.toJson(
                    hashMapOf(
                        "c" to gson.toJson(idList),
                        "csrf_token" to ""
                    )
                )
            )

            try {

                val response = RestUtil.send(
                    WY_DATA_MAP.get("musicInfoUrl")!!,
                    param, headerMap, 1, String::class.java
                )

                val content = response.body

                val map = gson.fromJson(
                    content,
                    object : TypeToken<HashMap<String, Any>>() {}.type
                ) as HashMap<String, Any>

                if (map.get("code").toString().toBigDecimal().toInt() != 200) {
                    log.warn("读取音乐ID${ids}详情失败", content)
                    return null
                }

                val songs = gson.fromJson(
                    gson.toJson(map.get("songs")),
                    object : TypeToken<ArrayList<WYSearchSong>>() {}.type
                ) as ArrayList<WYSearchSong>

                return if (songs.size > 0) songs.get(0) else null
            } catch (e: Exception) {
                log.error("读取我喜欢的音乐错误", e)
            }
            return null
        }
    }

}

fun main() {
    WYMusicUtil.searchMusic("周杰伦", 0)
//    WYMusicUtil.loginWYByPhone("761678772", "13228230943", "bcl201314")
}